/* This file is part of MyRename
 * Copyright (C) 2005-2009 Kai-Chieh Ku (kjackie@gmail.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

// DirTreeCtrl.cpp : implementation file
//

#include "stdafx.h"
#include "resource.h"
#include "MyRename.h"
#include "MyRenameDlg.h"
#include "preferences.h"
#include "language.h"
#include "others.h"
#include "memdc.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// CDirTreeCtrl

IMPLEMENT_DYNAMIC(CDirTreeCtrl, CTreeCtrl)
CDirTreeCtrl::CDirTreeCtrl()
	: m_hEditItem(0)
{
}

CDirTreeCtrl::~CDirTreeCtrl()
{
	m_treeImg.Detach(); //do not destroy system image list
}


BEGIN_MESSAGE_MAP(CDirTreeCtrl, CTreeCtrl)
	//{{AFX_MSG_MAP(CDirTreeCtr)
	ON_NOTIFY_REFLECT(TVN_SELCHANGED, OnTvnSelectChanged)
	ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, OnTvnItemExpanding)
	ON_NOTIFY_REFLECT(TVN_ENDLABELEDIT, OnTvnEndlabeledit)
	ON_NOTIFY_REFLECT(TVN_BEGINLABELEDIT, OnTvnBeginlabeledit)
	ON_NOTIFY_REFLECT(NM_RCLICK, OnNMRclick)
	ON_WM_CONTEXTMENU()
	ON_WM_SETFOCUS()
	ON_WM_KILLFOCUS()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CDirTreeCtrl::Init()
{
	{//load icons --->
		SHFILEINFO shFinfo;

		// Get the system image list using a "path" which is available on all systems.
		VERIFY(m_treeImg.m_hImageList = (HIMAGELIST) SHGetFileInfo(_T("."), 0, &shFinfo, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_SMALLICON));

		SetImageList(&m_treeImg, TVSIL_NORMAL);
	}//load icons <---

	// construct menu --->
	m_context.DestroyMenu();
	VERIFY(m_context.CreatePopupMenu());
	m_context.AppendMenu(MF_STRING, ID_FILE_OPEN, GetResString(IDS_SHELL_OPEN));
	m_context.AppendMenu(MF_SEPARATOR);
	m_context.AppendMenu(MF_STRING, ID_EDIT_COPY, GetResString(IDS_COPY));
	m_context.AppendMenu(MF_STRING, ID_EDIT_REPLACE, GetResString(IDS_EDIT_RENAME));
	// construct menu <---

	DeleteAllItems();
	DWORD i;
	{//insert drives --->
		m_drive_str[0] = _T('\0');

		const DWORD len = GetLogicalDriveStrings(DRIVELEN, m_drive_str);
		DWORD disks = len >> 2;
		HTREEITEM titem;

		SHFILEINFO shInfo;
		int iIcon;
		for (i = 0;i < disks;i++) {
			shInfo.szDisplayName[0] = _T('\0');
			VERIFY(SHGetFileInfo(m_drive_str + (i << 2), 0, &shInfo, sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_SMALLICON));
			iIcon = shInfo.iIcon;
			DestroyIcon(shInfo.hIcon);
			VERIFY(SHGetFileInfo(m_drive_str + (i << 2), 0, &shInfo, sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_OPENICON | SHGFI_SMALLICON | SHGFI_DISPLAYNAME));
			DestroyIcon(shInfo.hIcon);
			titem = InsertItem(TVIF_IMAGE | TVIF_PARAM | TVIF_SELECTEDIMAGE | TVIF_TEXT, shInfo.szDisplayName, iIcon, shInfo.iIcon, 0, 0, (LPARAM) (m_drive_str + (i << 2)), TVI_ROOT, TVI_LAST);
			InsertChildren(titem, true);
		}
	}//insert drives <---

	{//get default drive --->
		LPTSTR envdrive[] = {_T("SYSTEMDRIVE"), _T("HOMEDRIVE")};

		for (i = 0;i < 2 && (thePrefs.sLastPath.IsEmpty() || thePrefs.sLastPath == _T("\\"));i++)
			thePrefs.sLastPath.Format(_T("%s\\"), _tgetenv(envdrive[i]));
		if (thePrefs.sLastPath == _T("\\"))
			thePrefs.sLastPath = m_drive_str;
	}//get default drive <---
}

void CDirTreeCtrl::Localize()
{
	m_context.ModifyMenu(ID_FILE_OPEN, MF_BYCOMMAND, ID_FILE_OPEN, GetResString(IDS_SHELL_OPEN));
	m_context.ModifyMenu(ID_EDIT_COPY, MF_BYCOMMAND, ID_EDIT_COPY, GetResString(IDS_COPY));
	m_context.ModifyMenu(ID_EDIT_REPLACE, MF_BYCOMMAND, ID_EDIT_REPLACE, GetResString(IDS_EDIT_RENAME));
}

void CDirTreeCtrl::InsertChildren(HTREEITEM parent, bool fast /* =false */)
{
	CString thePath;
	CFileFind finder;
	BOOL bFound;

	GetFullPath(parent, thePath);

	DeleteChildren(parent);

	if (!PathFileExists(thePath))
		return;

	bFound = finder.FindFile(thePath + _T("*.*"));

	SHFILEINFO shInfo;
	int iIcon;

	while (bFound) {
		bFound = finder.FindNextFile();
		if (!finder.IsDots() && finder.IsDirectory()) {
			shInfo.szDisplayName[0] = _T('\0');
			VERIFY(SHGetFileInfo(finder.GetFilePath(), 0, &shInfo, sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_SMALLICON));
			iIcon = shInfo.iIcon;
			DestroyIcon(shInfo.hIcon);
			VERIFY(SHGetFileInfo(finder.GetFilePath(), 0, &shInfo, sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_OPENICON | SHGFI_SMALLICON));
			DestroyIcon(shInfo.hIcon);
			InsertItem(TVIF_IMAGE | TVIF_PARAM | TVIF_SELECTEDIMAGE | TVIF_TEXT, finder.GetFileName(), iIcon, shInfo.iIcon, 0, 0, 0, parent, TVI_LAST);
			if (fast) break;
		}
	}
}

void CDirTreeCtrl::DeleteChildren(HTREEITEM parent)
{
	HTREEITEM dchild, child = GetChildItem(parent);

	while (child != NULL) {
		dchild = child;
		child = GetNextSiblingItem(child);
		DeleteItem(dchild);
	}
}

void CDirTreeCtrl::GetFullPath(HTREEITEM item, CString &path)
{
	HTREEITEM parentItem = GetParentItem(item);

	if (parentItem != NULL) {
		GetFullPath(parentItem, path);
		path.AppendFormat(_T("%s\\"), GetItemText(item));
	}
	else
		path = (LPCTSTR) GetItemData(item);
}

HTREEITEM CDirTreeCtrl::FindSiblingItem(HTREEITEM from, LPCTSTR text)
{
	if (from == NULL)
		return NULL;
	CString key(text), item_str;
	key.MakeLower();

	for (;from;from = GetNextSiblingItem(from)) {
		if (GetItemData(from))
			item_str = (LPCTSTR) GetItemData(from);
		else
			item_str = GetItemText(from);

		item_str.MakeLower();

		if (item_str == key)
			return from;
	}

	return NULL;
}

HTREEITEM CDirTreeCtrl::SelectPath(LPCTSTR path)
{
	if (!PathFileExists(path) || !PathIsDirectory(path))
		return NULL;

	CString thePath(path);
	CString token;
	HTREEITEM curItem = GetRootItem();
	int curpos = 0;

	token = thePath.Tokenize(_T("\\"), curpos);
	// expand drive node --->
	if (!token.IsEmpty()) {
		token += _T("\\");
		curItem = FindSiblingItem(curItem, token);
		if (curItem != NULL)
			Expand(curItem, TVE_EXPAND);
		else
			return NULL;
	}
	// expand drive node <---

	token = thePath.Tokenize(_T("\\"), curpos);
	while (!token.IsEmpty()) {
		curItem = GetChildItem(curItem);
		curItem = FindSiblingItem(curItem, token);
		if (curItem != NULL) {
			token = thePath.Tokenize(_T("\\"), curpos);
			if (!token.IsEmpty())
				Expand(curItem, TVE_EXPAND);
			else
				SelectItem(curItem);
		}
		else
			return NULL;
	}

	return curItem;
}

HTREEITEM CDirTreeCtrl::ExpandPath(LPCTSTR path)
{
	HTREEITEM theItem = SelectPath(path);
	if (theItem != NULL)
		Expand(theItem, TVE_EXPAND);
	return theItem;
}

HTREEITEM CDirTreeCtrl::SelectChildItem(LPCTSTR text)
{
	HTREEITEM curItem = GetSelectedItem();
	if (curItem != NULL) {
		Expand(curItem, TVE_EXPAND);
		curItem = GetChildItem(curItem);
		if (curItem != NULL) {
			curItem = FindSiblingItem(curItem, text);
			if (curItem != NULL) {
				SelectItem(curItem);
				return curItem;
			}
		}
	}
	return NULL;
}

void CDirTreeCtrl::UpdateMenu()
{
	bool bSel = (GetSelectedItem() != NULL);
	m_context.EnableMenuItem(ID_FILE_OPEN, MF_BYCOMMAND | (bSel ? MF_ENABLED : MF_GRAYED));
	m_context.EnableMenuItem(ID_EDIT_COPY, MF_BYCOMMAND | (bSel ? MF_ENABLED : MF_GRAYED));
	m_context.EnableMenuItem(ID_EDIT_REPLACE, MF_BYCOMMAND | (bSel ? MF_ENABLED : MF_GRAYED));
}

// CDirTreeCtrl message handlers

void CDirTreeCtrl::OnTvnSelectChanged(NMHDR *pNMHDR, LRESULT *pResult)
{
	CWaitCursor wait;

	LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);

	if (thePrefs.all_ready) {
		HTREEITEM nowItem;

		nowItem = pNMTreeView->itemNew.hItem;

		GetFullPath(nowItem, thePrefs.sLastPath);
		MAINWND->setTitle(thePrefs.sLastPath);
		FILEWND->UpdateList(true, &thePrefs.fnMask);
	}
	*pResult = 0;
}

void CDirTreeCtrl::SetSelectPath(CString strPath)
{
	thePrefs.sLastPath = strPath;
	SelectPath(strPath);
}

void CDirTreeCtrl::OnTvnItemExpanding(NMHDR *pNMHDR, LRESULT *pResult)
{
	CWaitCursor wait;

	LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);

	HTREEITEM nowItem, childItem;

	nowItem = pNMTreeView->itemNew.hItem;

	if (pNMTreeView->action == TVE_EXPAND) { //ensure it's expanding
		InsertChildren(nowItem);
		childItem = GetChildItem(nowItem);

		while (childItem != NULL) {
			InsertChildren(childItem, true);
			childItem = GetNextSiblingItem(childItem);
		}
	}

	SelectItem(nowItem);

	*pResult = 0;
}


void CDirTreeCtrl::OnEditCopy()
{
	HTREEITEM selItem = GetSelectedItem();

	if (selItem == NULL)
		return;

	if (!OpenClipboard()) {
		MessageBox(_T("Can not open clip board!!"));
		return;
	}

	CString copydata = GetItemText(selItem);

	HGLOBAL data;
	LPTSTR ptr;
	size_t len = copydata.GetLength();

	data = GlobalAlloc(GMEM_MOVEABLE, sizeof(TCHAR) * (len + 1));
	ptr = (LPTSTR) GlobalLock(data);
	memcpy(ptr, copydata.GetBuffer(), sizeof(TCHAR) * (len + 1));
	ptr[len] = _T('\0');
	GlobalUnlock(data);

	if (SetClipboardData(CF_UNICODETEXT, data) == NULL)
		MessageBox(_T("Unable to set Clipboard data!!"));

	CloseClipboard();
}

void CDirTreeCtrl::OnEditUndo()
{
	MAINWND->SendMessage(WM_COMMAND, ID_EDIT_UNDO);
}

void CDirTreeCtrl::OnSetFocus(CWnd* pOldWnd)
{
	CTreeCtrl::OnSetFocus(pOldWnd);
}

void CDirTreeCtrl::OnKillFocus(CWnd* pNewWnd)
{
	CTreeCtrl::OnKillFocus(pNewWnd);
}

void CDirTreeCtrl::OnContextMenu(CWnd* /*pWnd*/, CPoint /*point*/)
{
	CRect rc, rcWin;
	CPoint pt;
	HTREEITEM selItem = GetSelectedItem();

	GetClientRect(rcWin);
	try {
		if (selItem) { //an item is selected
			if (GetItemRect(selItem, rc, TRUE) && (rcWin.PtInRect(rc.TopLeft()) 
				|| rcWin.PtInRect(rc.BottomRight()))) //the item is visible
			{
				ClientToScreen(rc);
				pt = rc.BottomRight();
			}
			else
				throw 1;
		}
		else
			throw 1;
	} catch (const int ex) {
		if (ex == 1) {
			GetWindowRect(rc);
			pt = rc.TopLeft();
		}
		else
			throw ex;
	}

	UpdateMenu();
	m_context.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this);
}

/*
	Unfortunately, there are some problem with WM_CONTEXTMENU notify,
	it doesn't notify when user press right botton on mouse.

	Therefore, we should extra add this function to notify right click.
*/
void CDirTreeCtrl::OnNMRclick(NMHDR *pNMHDR, LRESULT *pResult)
{
	POINT cursor;
	GetCursorPos(&cursor);

	TVHITTESTINFO hitInfo = {0};

	hitInfo.pt = cursor;
	bool bToUpdate = false;
	ScreenToClient(&hitInfo.pt);
	HitTest(&hitInfo);

	if (hitInfo.flags & TVHT_ONITEM && hitInfo.hItem != GetSelectedItem()) {
		FILEWND->LockUpdate();
		bToUpdate = true;
		SelectItem(hitInfo.hItem);
	}

	UpdateMenu();
	m_context.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, cursor.x, cursor.y, this);

	if (bToUpdate) {
		FILEWND->UnlockUpdate();
		FILEWND->UpdateList(true, &thePrefs.fnMask);
	}

	*pResult = 0;
}

BOOL CDirTreeCtrl::OnCommand(WPARAM wParam, LPARAM lParam)
{
	switch (HIWORD(wParam)) {
		case 0: //from menu items
		case 1: //from accelerators
			switch (LOWORD(wParam)) {
				case ID_EDIT_UNDO:
					MAINWND->SendMessage(WM_COMMAND, ID_EDIT_UNDO);
					return TRUE;
				case ID_EDIT_COPY:
					OnEditCopy();
					return TRUE;
				case ID_FILE_OPEN:
					{
						HTREEITEM hItem = GetSelectedItem();
						if (hItem) {
							CString path;
							GetFullPath(hItem, path);
							ShellExecute(NULL, _T("open"), path, NULL, path, SW_SHOWMAXIMIZED);
						}
					}
					return TRUE;
				case ID_EDIT_REPLACE:
					{
						HTREEITEM hItem = GetSelectedItem();
						if (hItem)
							EditLabel(hItem);
					}
					return TRUE;
			}
	}

	return CTreeCtrl::OnCommand(wParam, lParam);
}

void CDirTreeCtrl::OnTvnBeginlabeledit(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMTVDISPINFO pTVDispInfo = reinterpret_cast<LPNMTVDISPINFO>(pNMHDR);

	if (pTVDispInfo->item.lParam) {
		*pResult = 1; //cancel edit
		return;
	}

	m_sEditName = pTVDispInfo->item.pszText;
	m_hEditItem = pTVDispInfo->item.hItem;

	theApp.m_bPostReturn = false;

	*pResult = 0;
}

void CDirTreeCtrl::OnTvnEndlabeledit(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMTVDISPINFO pTVDispInfo = reinterpret_cast<LPNMTVDISPINFO>(pNMHDR);

	*pResult = 0;

	if (m_hEditItem && m_hEditItem == pTVDispInfo->item.hItem &&
		pTVDispInfo->item.pszText && m_sEditName != pTVDispInfo->item.pszText)
	{
		CString sOld, sNew;
		GetFullPath(m_hEditItem, sOld);
		sOld.TrimRight('\\');
		int pos = sOld.ReverseFind('\\');
		if (pos != -1)
			sNew.Format(_T("%s%s"), sOld.Left(pos + 1), pTVDispInfo->item.pszText);
		if (MoveFile(sOld, sNew)) {
			thePrefs.sLastPath = sNew;
			MAINWND->setTitle(thePrefs.sLastPath);
			*pResult = 1;
		}
	}

	m_hEditItem = 0;
	theApp.m_bPostReturn = true;
}

LRESULT CDirTreeCtrl::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message) {
		case WM_ERASEBKGND:
			return TRUE;
		case WM_PAINT:
			return CTreeCtrl::DefWindowProc(WM_PAINT, (WPARAM)(HDC) MemDC(m_hWnd), 0);
		case WM_KEYDOWN:
		case WM_SYSKEYDOWN:
			{
				bool bCtrl = IsCtrlPressed();
				switch (wParam) {
					case 'C':
						if (bCtrl) {
							SendMessage(WM_COMMAND, 0x00010000 | ID_EDIT_COPY);
							return TRUE;
						}
						break;
					case 'Z':
						if (bCtrl) {
							SendMessage(WM_COMMAND, 0x00010000 | ID_EDIT_UNDO);
							return TRUE;
						}
						break;
					case VK_F2:
						SendMessage(WM_COMMAND, 0x00010000 | ID_EDIT_REPLACE);
						return TRUE;
					case VK_RETURN:
						CTRPANWND->SendMessage(WM_COMMAND, 0x00010000 | IDC_BTN_RENAME);
						return TRUE;
				}
			}
			break;
	}

	return CTreeCtrl::DefWindowProc(message, wParam, lParam);
}
